iT邦幫忙

2024 iThome 鐵人賽

0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 31

小型購物車專案 DLC 高併發:導入Redis、RabbitMQ、Docker

  • 分享至 

  • xImage
  •  

先前的部分可以從Day15 觀看,看完Day22後,再來看這一篇,把原本的專案進化成高併發專案。

這篇文章的大綱,提供給大家參考。

Redis

為什麼要使用Redis

需要頻繁讀取資料時,資料庫的讀取速度可能會成為瓶頸,特別是高併發的情況。

使用Redis可以將資料快取,專案就能從RAM(Redis)讀取資料,減少讀取資料庫(MariaDB)的次數,降低延遲。

Redis是什麼

Redis是一種NoSQL資料庫,可以用於快取和即使存取資料。

跟MariaDB不同,Redis是採用key-value的格式儲存資料,並且使用RAM保存Redis資料庫的內容,因此讀取和寫入的速度極快。

如何安裝和啟動Redis

Redis只支援Unix、Linux系統,所以Windows需要使用Docker,才能安裝和啟動Redis。

以下是如何使用Docker安裝Redis的步驟:

  1. 安裝Docker,前往官網下載安裝Docker。請選擇AMD64,而不是ARM64版本。
  2. 啟動Docker,可能會有問卷,可以直接跳過。
  3. 啟動命令提示字元,輸入docker pull redis:latest,下載Redis的Docker映像檔。
  4. 啟用Redis容器,這樣才能在主機使用Redis資料庫。
docker run --name my-redis -d redis

完成以上步驟後,就能正常使用Redis資料庫。

接下來,我們要讓專案能夠用Redis快取。


在專案啟用Redis快取

pom.xml,添加Redis套件。

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

在application.properties,加上設定,讓專案能夠連接Redis。

spring.data.redis.port=6379
spring.data.redis.host=localhost

添加Config/RedisConfig.java,用來設定如何使用Redis進行快取。

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        //建立RedisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //使用factory來建立Redis連接
        redisTemplate.setConnectionFactory(factory);

        //設定ObjectMapper,使用Jackson時需要ObjectMapper

        ObjectMapper objectMapper = new ObjectMapper();
        //可以接收Object所有的屬性,private的屬性也包含在內
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //處理Object的資料形態(Entity),以便Jackson在Deserialize時自動賦予資料形態
        objectMapper.activateDefaultTyping(
                //允許所有的資料形態
                LaissezFaireSubTypeValidator.instance,
                //會處理除了Final以外的資料形態
                ObjectMapper.DefaultTyping.NON_FINAL,
                //將Object轉換成JSON,夾帶Object的資料形態
                JsonTypeInfo.As.WRAPPER_ARRAY
        );
        //Deserialize時忽略未知的屬性,不會直接觸發Exception
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        //使用上面設定好的objectMapper來建立Jackson2JsonRedisSerializer,用它來Serialize、Deserialize
        //把Java的Object轉換成JSON格式(Serialize)、將JSON轉換回Java的Object(Deserialize)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        //Key會使用StringRedisSerializer,Serialize成字串,或從字串Deserialize
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        //Value會使用jackson2JsonRedisSerializer,Serialize成JSON,或從JSON Deserialize
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //確認對redisTemplate的設定,並啟用
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate){
        //管理Redis Cache時,不加鎖,提高性能表現
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        //採用預設的Cache設定,serialize的方式採用redisTemplate中的設定,也就是jackson2JsonRedisSerializer
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }
}

將資料寫入Redis和刪除快取

我們要在Service設定資料的快取。

從Service/UserService.java開始,修改findUserByEmail、findUserById的部分。

如果能夠在快取找到,就立刻回傳。否則,從資料庫取得後存入快取,這樣下次就能更快的讀取。

設定快取過期的時間是30到39分鐘,因為User的內容不會頻繁變化,而且各種操作都需要查詢User,設定較長的過期時間比較適合。

我們使用隨機的過期時間,是為了避免快取同時過期,導致同時要快取所有過期的資料,並且如果同時有湧入大量的請求,就會發生延遲。

那麼設定很長的過期時間,讓Redis一直保存,是否就能避免以上情形。確實能避免,但是會有另一個問題,RAM會被大量占用,造成浪費。

@Service
public class UserService {
    //...
    private final RedisTemplate<String, Object> redisTemplate;
    private final RabbitTemplate rabbitTemplate;
    private final int USER_REDIS_CACHE_MINUTES = 30;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, JWTProvider jwtProvider, RedisTemplate<String, Object> redisTemplate, RabbitTemplate rabbitTemplate) {
        //...
        this.redisTemplate = redisTemplate;
        this.rabbitTemplate = rabbitTemplate;
    }

    public void createUser(User user) throws Exception {
        //...
    }

    public User findUserByEmail(String email){
        String cacheKey = "user:email:" + email;
        User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey);
        if (cachedUser != null) {
            return cachedUser;
        }

        User user = userRepository.findByEmail(email);
        if (user != null) {
			int random_delay = random.nextInt(10);
            redisTemplate.opsForValue().set(cacheKey, user, USER_REDIS_CACHE_MINUTES + random_delay, TimeUnit.MINUTES);
        }

        return user;
    }

    public User findUserByJWT(String jwt) throws Exception{
        //...
    }

    public User findUserById(Long id) throws Exception{
        String cacheKey = "user:id:" + id;
        User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey);
        if (cachedUser != null) {
            return cachedUser;
        }

        Optional<User> opt = userRepository.findById(id);
        if(opt.isPresent()){
            User user = opt.get();
			int random_delay = random.nextInt(10);
            redisTemplate.opsForValue().set(cacheKey, user, USER_REDIS_CACHE_MINUTES + random_delay, TimeUnit.MINUTES);
            return user;
        }
        throw new Exception("Error: User not found with id: " + id);
    }
}

ProductService.java

大部分和UserService.java的部分相同,這邊多了刪除商品時,也從快取刪除資料,避免錯誤讀取到已經被刪除的內容。

@Service
public class ProductService {
    //...
    private final RedisTemplate<String, Object> redisTemplate;
    private final int PRODUCT_REDIS_CACHE_MINUTES = 1;
    Random random = new Random();

    public ProductService(ProductRepository productRepository, RedisTemplate<String, Object> redisTemplate) {
        //...
        this.redisTemplate = redisTemplate;
    }

    public Product addProduct(Product product) {
        //...
    }

    public String deleteProduct(Long id){
        productRepository.deleteById(id);
        //刪除Redis快取
        String cacheKey = "product:" + id;
        redisTemplate.delete(cacheKey);
        return "Product deleted successfully";
    }

    public Product getProductById(Long id) throws Exception{
        //從Redis取得快取資料
        String cacheKey = "product:" + id;
        Product cachedProduct = (Product) redisTemplate.opsForValue().get(cacheKey);
        if (cachedProduct != null) {
            return cachedProduct;
        }
        //沒有在Redis快取中,必須從資料庫取得
        Optional<Product> opt = productRepository.findById(id);
        if(opt.isPresent()){
            Product product = opt.get();
            //存入Redis快取,設定過期規則
            int random_delay = random.nextInt(3);
            redisTemplate.opsForValue().set(cacheKey, product, PRODUCT_REDIS_CACHE_MINUTES + random_delay, TimeUnit.MINUTES);
            return product;
        }
        throw new Exception("Product not found");
    }

    public Page<Product> getProductsByFilter(String category, Integer minPrice, Integer maxPrice,
                                             String sort, Integer pageNumber, Integer pageSize) {
        //取得第pageNumber(頁數是從0開始),每頁有pageSize個產品
        Pageable pageable = PageRequest.of(pageNumber, pageSize);
        List<Product> products;

        //在快取中尋找
        String cacheKey = "products:filter:category:" + category +
                ":minPrice:" + minPrice +
                ":maxPrice:" + maxPrice +
                ":sort:" + sort +
                ":page:" + pageNumber +
                ":size:" + pageSize;
        List<Product> cachedProducts = (List<Product>) redisTemplate.opsForValue().get(cacheKey);

        if (cachedProducts != null) {
            //直接將快取的資料放入products
            products = cachedProducts;
        }
        else{
            //沒在快取中
            //從資料庫取得符合條件的產品
            products = productRepository.findProductsByFilter(category, minPrice, maxPrice, sort);
            int random_delay = random.nextInt(3);
            redisTemplate.opsForValue().set(cacheKey, products, PRODUCT_REDIS_CACHE_MINUTES + random_delay, TimeUnit.MINUTES);
        }

        //設定從哪裡開始取資料,哪裡結束
        int startIndex = (int) pageable.getOffset();//取得指定頁數前有多少資料,等於pageNumber*pageSize
        //如果剩餘的資料>=pageSize,就只取pageSize筆。
        //如果剩餘的資料<pageSize,將剩下的資料全部取得。
        int endIndex = Math.min((startIndex + pageable.getPageSize()), products.size());

        //從過濾後的產品列表,截取對應頁數和數量的產品
        List<Product> pageContent = products.subList(startIndex, endIndex);

        //回傳內容、分頁資訊(頁碼、一頁有幾筆資料)、符合過濾條件的產品數量
        return new PageImpl<>(pageContent, pageable, products.size());
    }
}


剩下的部分都和前面相同

OrderService.java

@Service
public class OrderService {
    //...
    private final RedisTemplate<String, Object> redisTemplate;
    private final int ORDER_REDIS_CACHE_MINUTES = 1;
    Random random = new Random();

    public OrderService(OrderRepository orderRepository, RedisTemplate<String, Object> redisTemplate){
        //...
        this.redisTemplate = redisTemplate;
        //...
    }

    //建立Stripe支付的Session
    //import Session時選擇com.stripe.model.checkout
    public Session createCheckoutSession(int amount) throws StripeException {
        //...
    }

    //建立訂單
    public Order createOrder(String sessionId, Integer totalPrice, String status, String url, Long userId) throws Exception {
        //...
    }

    //使用用戶ID查詢用戶的訂單資訊,查詢時同時更新資料
    public List<Order> findOrderByUserId(Long userId) throws Exception {
        String cacheKey = "orders:userId:" + userId;
        List<Order> cachedOrders = (List<Order>) redisTemplate.opsForValue().get(cacheKey);
        if (cachedOrders != null) {
            return cachedOrders;
        }

        List<Order> orders = orderRepository.findOrderByUserId(userId);
        List<Order> updated_orders = new ArrayList<>();
        for(Order order: orders){
            updateOrder(order.getId());
            updated_orders.add(order);
        }
        int random_delay = random.nextInt(3);
        redisTemplate.opsForValue().set(cacheKey, updated_orders, ORDER_REDIS_CACHE_MINUTES + random_delay, TimeUnit.MINUTES);
        return updated_orders;
    }

    //更新訂單資訊的付款狀態
    public void updateOrder(Long id) throws Exception {
        //...
    }

}

CartItemService.java

@Service
public class CartItemService {
    //...
    private final RedisTemplate<String, Object> redisTemplate;
    private final int CARTITEM_REDIS_CACHE_MINUTES = 1;
    Random random = new Random();

    public CartItemService(CartItemRepository cartItemRepository, UserService userService, RedisTemplate<String, Object> redisTemplate) {
        //...
        this.redisTemplate = redisTemplate;
    }

    //檢查商品是否在購物車中
    public CartItem isCartItemInCart(Cart cart, Product product) {
        //...
    }

    //創建並儲存cartItem到資料庫中
    public CartItem createCartItem(CartItem cartItem) {
        //...
    }

    //更新CartItem,重新計算數量和價格,並儲存到資料庫。
    public CartItem updateCartItem(Long userId, Long id, CartItem cartItem) throws Exception {
        CartItem item = findCartItemById(id);
        User user = userService.findUserById(item.getCart().getUser().getId());
        //確認發送請求的用戶和購物車的擁有者是同一人
        if(user.getId().equals(userId)) {
            item.setQuantity(cartItem.getQuantity());
            item.setPrice(item.getQuantity() * item.getProduct().getPrice());

            //刪除快取
            String cacheKey = "cartItem:" + id;
            redisTemplate.delete(cacheKey);
        }

        return cartItemRepository.save(item);
    }

    //用ID查詢CartItem
    public CartItem findCartItemById(Long id) throws Exception {
        //尋找快取
        String cacheKey = "cartItem:" + id;
        CartItem cachedCartItem = (CartItem) redisTemplate.opsForValue().get(cacheKey);
        if (cachedCartItem != null) {
            return cachedCartItem;
        }
        //cache miss
        Optional<CartItem> optionalCartItem = cartItemRepository.findById(id);
        if(optionalCartItem.isPresent()) {
            CartItem cartItem = optionalCartItem.get();
            int random_delay = random.nextInt(3);
            redisTemplate.opsForValue().set(cacheKey, cartItem, CARTITEM_REDIS_CACHE_MINUTES + random_delay, TimeUnit.SECONDS);
            return cartItem;
        }
        throw new Exception("CartItem not found with id : " + id);
    }

    //移除購物車的商品
    public void removeCartItem(Long userId, Long id) throws Exception {
        CartItem item = findCartItemById(id);
        User user = userService.findUserById(item.getCart().getUser().getId());
        User reqUser = userService.findUserById(userId);
        if(user.getId().equals(reqUser.getId())) {
            cartItemRepository.deleteById(id);
            //刪除快取
            String cacheKey = "cartItem:" + id;
            redisTemplate.delete(cacheKey);
            return;
        }
        throw new Exception("Can't remove another users item");
    }
}

下個部分是:完成註冊後,發送通知郵件到信箱。


RabbitMQ

RabbitMQ是什麼

RabbitMQ的核心功能是充當訊息傳遞的中間層,將訊息送到Queue,Queue根據設定轉發到Comsumers。

RabbitMQ還能透過多種不同類型的Exchanges(交換器),靈活地將消息路由到不同的Queue。

為什麼要用RabbitMQ

某些耗時的操作會影響專案的回應速度,使用RabbitMQ可以改善。

以用戶註冊為例,當新用戶註冊後,系統會發送Email到信箱。

如果沒有RabbitMQ,用戶需要等待Email的流程結束後,才能繼續操作,導致使用者的體驗不佳。

導入RabbitMQ後,可以將耗時的操作(發送Email)轉移到Queue中,用戶就能立刻得到註冊成功的結果。

發送Email則是由Comsumer非同步處理,避免系統被長時間阻塞。

RabbitMQ如何安裝

雖然Windows可以直接安裝RabbitMQ,但是需要另外安裝Erlang。

建議使用Docker安裝比較容易。

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-management

專案導入Email和RabbitMQ

pom.xml,添加Mail、AMQP套件。

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

修改application.properties,添加email和RabbitMQ的設定。

spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${GMAIL_ADDRESS}
spring.mail.password=${GMAIL_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

在繼續之前,我們先取得應用程式密碼。

  1. 進入Google帳戶,切換到安全性頁面,確認兩步驟驗證開啟。
  2. 使用最上方的搜尋工具,輸入並選擇「應用程式密碼」。
  3. 在應用程式名稱輸入「Spring Boot SMTP」後,按下建立。
  4. 將畫面上的16字元的密碼,去除空白後,寫入.env的GMAIL_PASSWORD。

這樣我們就能使用Gmail免費發送郵件。


RabbitMQ設定

建立Config/RabbitMQConfig.java,設定RabbitMQ的Queue的Exchange。

@Configuration
public class RabbitMQConfig {
    public static final String QUEUE_NAME = "emailQueue";
    public static final String EXCHANGE_NAME = "emailExchange";

    @Bean
    //RabbitMQ產生持久化的Queue,可以依序完成要求的功能(送Email)
    public Queue queue() {
        return new Queue(QUEUE_NAME, true);
    }

    @Bean
    //RabbitMQ Exchange,接收Producer的要求,送到對應的Queue中
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }

    @Bean
    //將Queue綁定到指定的Exchange,當routingKey相同時,才會分到Queue中
    public Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("email.routing.key");
    }
}

建立Service/EmailConsumer.java,設定消費者從Queue接收到訊息時的動作。

@Component
public class EmailConsumer {
    private final EmailService emailService;

    public EmailConsumer(EmailService emailService){
        this.emailService = emailService;
    }

    //當指定的Queue接收到訊息時,會自動執行裡面的內容
    @RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
    public void receiveMessage(String to) {
        //發送郵件
        emailService.sendSimpleEmail(to);
    }
}


發送Email到信箱

新增Service/EmailService.java,處理發送Email的細節。

@Service
public class EmailService {
    private final JavaMailSender javaMailSender;

    public EmailService(JavaMailSender javaMailSender){
        this.javaMailSender = javaMailSender;
    }

    //處理送出Email的細節
    public void sendSimpleEmail(String to) {
        Dotenv dotenv = Dotenv.load();

        //編寫郵件內容
        SimpleMailMessage message = new SimpleMailMessage();
        //寄到哪邊
        message.setTo(to);
        //Email主旨
        message.setSubject("Welcome to Shopping Cart, " + to + " !");
        //Email內容
        message.setText("Thank you for choosing Shopping Cart. We look forward to serving you and making your shopping experience enjoyable.\n" +
                "Best regards,\n" +
                "The Shopping Cart Team");
        //寄件人是誰
        message.setFrom(dotenv.get("GMAIL_ADDRESS"));
        //送出Email
        javaMailSender.send(message);
    }
}

修改UserService.java,在註冊成功時將發送Email透過Exchange傳送到對應的Queue。

@Service
public class UserService {
    //...
    private final RabbitTemplate rabbitTemplate;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, JWTProvider jwtProvider, RedisTemplate<String, Object> redisTemplate, RabbitTemplate rabbitTemplate) {
        //...
        this.rabbitTemplate = rabbitTemplate;
    }

    public void createUser(User user) throws Exception {
        //找尋資料庫中是否有使用同樣的email的用戶
        User isEmailExists = userRepository.findByEmail(user.getEmail());

        //如果有,代表這個email被註冊了
        if(isEmailExists != null) {
            throw new Exception("Error: Email is already registered.");
        }

        User createdUser = new User();
        createdUser.setEmail(user.getEmail());
        //將密碼加密,提升安全性
        createdUser.setPassword(passwordEncoder.encode(user.getPassword()));
        //寄出恭喜註冊的郵件到註冊Email
        sendEmail(user.getEmail());
        userRepository.save(createdUser);
    }

    //使用RabbitMQ完成發出Email的功能
    public void sendEmail(String to) {
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "email.routing.key", to);
    }

	//...
}

註冊的結果是這樣

覺得延遲很嚴重?那來看看沒有RabbitMQ的情形,延遲更長了3702ms。

等1秒還可以接受。等3秒的話,我想大部分的人會認為是不是當機了。

Docker一鍵部署專案

安裝MariaDB、Redis、RabbitMQ的過程,有些人可能認為很困難。

現在,我們使用Docker打包一切,從環境設定到自動編譯和啟動。

只要準備好專案的原始碼和.env,然後專案的根目錄編寫docker-compose.yml、Dockerfile,就能自動完成這一切。

docker-compose.yml

我們先完成docker-compose.yml,處理環境(MariaDB、Redis、RabbitMQ)相關的設定。

services:
  db:
    #使用最新的mariadb穩定版
    image: mariadb:latest
    #將容器取名為mariadb_book
    container_name: mariadb_cart
    #意外停止後,會自動重新啟動
    restart: always
    environment:
      #設定mariadb root的密碼
      MARIADB_ROOT_PASSWORD: 12345678
      #建立資料庫
      MARIADB_DATABASE: cart_db
    #使用的port,AAAA:BBBB,AAAA是外部連接容器使用的port,BBBB是容器內部的port
    ports:
      - "3306:3306"
    #將Mariadb的資料存入這個位置,確保容器重新啟動時,資料庫的資料不會消失
    volumes:
      - db_data:/var/lib/mysql

  #redis相關的設定
  redis:
    image: redis:latest
    container_name: redis_cart
    restart: always
    ports:
     - "6379:6379"

  rabbitmq:
    image: rabbitmq:4-management
    container_name: rabbitmq_cart
    restart: always
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq

  #專案相關的設定
  app:
    #使用同目錄下的Dockerfile,來建立app容器
    build: .
    container_name: java_cart
    restart: always
    ports:
      - "8080:8080"
    #spring專案設定
    environment:
      #改寫application.yaml中的spring: datasource: url:
      #db是資料庫容器的名稱,這邊不能再使用localhost,會無法連接
      SPRING_DATASOURCE_URL: jdbc:mariadb://db:3306/cart_db
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: 12345678
      #改寫application.yaml中的spring: data: redis: host:
      #redis是容器的名稱
      SPRING_DATA_REDIS_HOST: redis
      #RabbitMQ設定
      SPRING_RABBITMQ_HOST: rabbitmq

volumes:
  db_data:
  rabbitmq_data:

Dockerfile

Dockerfile,完成自動編譯並執行的設定。

#使用maven
FROM maven:3-amazoncorretto-17 AS build

#將目錄下的檔案複製到app資料夾中
COPY . /app

#設定之後執行操作的位置
WORKDIR /app

#使用maven打包,不經過測試
RUN mvn clean package -DskipTests

#使用amazoncorretto
FROM amazoncorretto:17-alpine

#從上面build的部分取得jar,複製到新容器中
COPY --from=build /app/target/*.jar app.jar
#將.env複製到容器中
COPY ./.env .env

#使用port 8080
EXPOSE 8080

#啟動Spring Boot專案
ENTRYPOINT ["java", "-jar", "app.jar"]

我們就能使用一行命令,完成環境並啟動專案。

docker-compose up

JMeter壓力測試

使用JMeter來測試高併發專案

設定1000*4(Get Product By Filter、Get User Cart、Add To Cart、Find Order)*3=12000個Request,對於專案是個很大的挑戰。

測試的結果,我們先看最大的延遲是多少,從下圖可以得知為2845ms,在這樣的壓力下能得到這種結果已經很不錯了,而且是100%的通過,沒有任何錯誤。

最低的時候,可以達到5ms的低延遲表現。

我們的高併發專案通過了壓力測試,並且是在17秒內湧入12000個Request的情況下,驗證它擁有處理高併發的能力。


上一篇
Day30 前端專案:Vue.js(4)打包並部署到Linux
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言